package com.brayan.webapp.repository.general.country;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import javax.ejb.Singleton;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.brayan.webapp.model.general.country.Country;
import com.brayan.webapp.model.general.country.Country_;

@Singleton
public class CountryRepository {

	private static Logger LOGGER = Logger.getLogger(CountryRepository.class);

	@PersistenceContext(unitName = "ccms")
	EntityManager em;

	public static final String XML_FILE_PATH = "/data/countryInfo.xml";

	/**
	 * Load countries from a static XML file.
	 * 
	 * @param filePath
	 * @return
	 */
	protected List<Country> loadInitialCountryDataFromXML(String filePath) {
		List<Country> countryList = new ArrayList<Country>();

		final String FILE_PATH = filePath;
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		dbf.setNamespaceAware(false);

		// Document doc = db.parse(new URL(url).openStream());
		DocumentBuilder db = null;
		Document doc = null;
		try {
			db = dbf.newDocumentBuilder();
			InputStream is = this.getClass().getResourceAsStream(FILE_PATH);
			doc = db.parse(is);
			LOGGER.info("Parsed xml file successfully. (" + FILE_PATH + ")");
		} catch (Exception e) {
			LOGGER.error("Failed to load XML file (" + FILE_PATH + ").");
		}

		// root node: geonames
		if (doc == null) {
			LOGGER.error("Document was null. Stream from xml file (" + FILE_PATH + ") could not be parsed.");
		} else {
			Element rootElement = doc.getDocumentElement();
			LOGGER.info("Loading root element: " + rootElement.getNodeName());

			// Get country tags.
			NodeList nodeList = rootElement.getChildNodes();
			for (int i = 0; i < nodeList.getLength(); i++) {

				Node node = nodeList.item(i);
				if (node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName() == "country") {
					Country iCountry = new Country();
					Element country = (Element) node;

					Node countryNode = country.getElementsByTagName("countryName").item(0).getFirstChild();
					Node codeIsoAlpha3 = country.getElementsByTagName("isoAlpha3").item(0).getFirstChild();
					Node countryCodeNode = country.getElementsByTagName("countryCode").item(0).getFirstChild();
					Node currencyNode = country.getElementsByTagName("currencyCode").item(0).getFirstChild();
					Node continentNode = country.getElementsByTagName("continent").item(0).getFirstChild();
					Node languagesNode = country.getElementsByTagName("languages").item(0).getFirstChild();

					if (countryCodeNode == null || countryCodeNode.getNodeValue().isEmpty()) {
						LOGGER.warn("Could not inport an individual country:  " + countryNode.getNodeValue());
						continue;
					}
					iCountry.setCountryCode(countryCodeNode.getNodeValue());
					if (codeIsoAlpha3 == null || codeIsoAlpha3.getNodeValue().isEmpty()) {
						LOGGER.warn("Could not import an individual country: " + countryNode.getNodeValue());
						continue;
					}
					iCountry.setIsoAlpha3(codeIsoAlpha3.getNodeValue());
					iCountry.setCountryName(countryNode.getNodeValue());
					if (currencyNode != null)
						iCountry.setCurrencyCode(currencyNode.getNodeValue());
					iCountry.setContinentCode(continentNode.getNodeValue());
					if (languagesNode != null)
						iCountry.setLanguages(languagesNode.getNodeValue());

					LOGGER.info("Added " + iCountry.getCountryName());

					countryList.add(iCountry);
				}
			}
		}
		return countryList;
	}

	/**
	 * Insert countries into database. Redundancies save (by country code).
	 * 
	 * @param countryList
	 */
	public void insertCountries(List<Country> countryList) {
		LOGGER.info("Attempting to insert Country table records.");
		for (Country country : countryList) {
			if(findCountryByCode(country.getCountryCode()) == null)
				em.persist(country);
			else
				LOGGER.warn("Country " + country.getCountryCode() + " already exists. No need to insert.");			
		}
		LOGGER.info("Completed to insert Country table records.");
	}

	/**
	 * Deletes all countries and re-populates them.
	 */
	public void repopulateCountryRepo() {
		LOGGER.warn("Attempting to repopulate the Country table.");
		// deleteAllCountries();
		insertCountries(loadInitialCountryDataFromXML(XML_FILE_PATH));
		LOGGER.warn("Completed the repopulation of the Country table.");
	}

	/**
	 * Delete all countries.
	 */
	private void deleteAllCountries() {
		LOGGER.info("Attempting to delete all Country records.");
		Query deleteQuery = em.createNativeQuery("DELETE FROM Country");
		int deletedRecords = deleteQuery.executeUpdate();
		LOGGER.info("Completed to delete all Country records. Deleted " + deletedRecords + " records.");
	}

	public List<Country> getAllCountries() {
		CriteriaBuilder critBuilder = em.getCriteriaBuilder();
		CriteriaQuery<Country> criteriaQuery = critBuilder.createQuery(Country.class);
		Root<Country> countryRoot = criteriaQuery.from(Country.class);
		criteriaQuery.select(countryRoot);
		
		TypedQuery<Country> typedQuery = em.createQuery(criteriaQuery);
		return typedQuery.getResultList();
	}

	public List<Country> getAllCountriesOrderedByPriority() {

		CriteriaBuilder critBuilder = em.getCriteriaBuilder();
		CriteriaQuery<Country> criteriaQuery = critBuilder.createQuery(Country.class);
		Root<Country> countryRoot = criteriaQuery.from(Country.class);
		criteriaQuery.select(countryRoot);
		criteriaQuery.orderBy(
				critBuilder.asc(countryRoot.get(Country_.priorityGroup)),
				critBuilder.asc(countryRoot.get(Country_.countryName)));

		TypedQuery<Country> typedQuery = em.createQuery(criteriaQuery);
		return typedQuery.getResultList();
	}

	/**
	 * Fetch with the 2-digit country code.
	 * 
	 * @param countryCode
	 * @return
	 */
	public Country findCountryByCode(String countryCode) {

		CriteriaBuilder critBuilder = em.getCriteriaBuilder();
		CriteriaQuery<Country> criteriaQuery = critBuilder.createQuery(Country.class);
		Root<Country> countryRoot = criteriaQuery.from(Country.class);
		criteriaQuery.select(countryRoot);
		criteriaQuery.where(countryRoot.get(Country_.countryCode).in(countryCode));
		TypedQuery<Country> typedQuery = em.createQuery(criteriaQuery);
		Country fetchedCountry = null;
		try {
			fetchedCountry = typedQuery.getSingleResult();
		} catch (NoResultException nrEx) {
			LOGGER.info("Failed to find a country by 2-digit code (" + countryCode + ").");
			return null;
		}

		return fetchedCountry;
	}

	public Country findCountryByCountryName(String countryName) {

		CriteriaBuilder critBuilder = em.getCriteriaBuilder();
		CriteriaQuery<Country> criteriaQuery = critBuilder.createQuery(Country.class);
		Root<Country> countryRoot = criteriaQuery.from(Country.class);
		criteriaQuery.select(countryRoot);
		criteriaQuery.where(countryRoot.get(Country_.countryName).in(countryName));
		TypedQuery<Country> typedQuery = em.createQuery(criteriaQuery);
		Country fetchedCountry = null;
		try {
			fetchedCountry = typedQuery.getSingleResult();
		} catch (NoResultException nrEx) {
			LOGGER.info("Failed to find a country by country name (" + countryName + ").");
			return null;
		}
		return fetchedCountry;
	}

	public Country getCountry(String countryCode) {
		return em.find(Country.class, countryCode);
	}
}
